﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WINX32Lib;
using System.Windows.Forms;
using System.ComponentModel;

namespace AIRO.BFSerial
{
    public class WSState:EventArgs
    {
        private string _errMsg;
        private int _errCode;
        public WSState(int errCode, string errMsg)
        {
            this._errCode = errCode;
            this._errMsg = errMsg;
        }

        public string ErrMsg
        {
            get
            {
                return _errMsg;
            }
        }
        public int ErrCode
        {
            get
            {
                return _errCode;
            }
        }
    }

    public class WinSpec
    {
        private IDocFile wsDoc;
        private IExpSetup wsExp;
        private IDocFile wsOutput;
        private IDocFile wsDiffSpectra;
        private bool wsDiffInitialized;
        /// <summary>
        /// gen off frame
        /// </summary>
        private object wsFrame0;
        /// <summary>
        /// gen on frame
        /// </summary>
        private object wsFrame1;
        /// <summary>
        /// difference on frame
        /// </summary>
        private object wsDiff;
        private double[,] arrayWsDiff;
        private int outX, outY;
        private int expXDim=0;
        private long fltWnd;
        private bool keepSpec;
        private long cycles;

        const double spikeThreshould = 8;


        public event EventHandler<WSState> WSErrorEvent;
        public event EventHandler WSExpCompletedEvent;

        protected virtual void OnWSErrorEvent(WSState e)
        {
            EventHandler<WSState> handler = WSErrorEvent;
            if (handler != null)
            {
                handler(this, e);
            }
        }
        protected virtual void OnWSExpCompletedEvent(EventArgs e)
        {
            EventHandler handler = WSExpCompletedEvent;
            if (handler != null)
            {
                handler(this, e);
            }
        }


        


        //fmMain frm;
        /// <summary>This method is called on program startup.</summary>
        public WinSpec(fmMain frm)
        {        
            
            //this.frm = frm;
            backgroundWorker = new BackgroundWorker();
            InitializeBgWorker();
            
        }


        private System.ComponentModel.BackgroundWorker backgroundWorker;



        // Set up the BackgroundWorker object by 
        // attaching event handlers. 
        private void InitializeBgWorker()
        {            
            //backgroundWorker.WorkerReportsProgress = true;
            backgroundWorker.DoWork += new DoWorkEventHandler(BgWorker_DoWork);
            backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
            backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
        }

        void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            OnWSExpCompletedEvent(new EventArgs());
        }

        void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (((WSState)e.UserState).ErrCode != 0)
            {
                OnWSErrorEvent((WSState)e.UserState);
            }
        }

        private void BgWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = (BackgroundWorker)sender;
            this.RunExp(worker, e);
            //throw new NotImplementedException();
        }

        public void RunExpAsync()
        {
            backgroundWorker.RunWorkerAsync();
        }

        /// <summary>This method is called on serial start.</summary>        
        public void ExpInit(int x, int y, long cycles, long flt, bool keep)
        {
            wsDoc = null;
            outX = x;
            outY = y;
            expXDim = 0;
            fltWnd = flt;
            keepSpec = keep;
            this.cycles = cycles;
            DocInfo di = new DocInfoClass();
            di.X = outX;
            di.Y = outY;
            di.Z = 1;
            di.dataType = dataType.X_DOUBLE;
            di.bShowWindow = true;
            di.bAppend = false;
            di.FileType = docType.dt_SPE;
            di.Name = "Output";
            wsOutput = new DocFileClass();
            if (!wsOutput.OpenNew("", di))
            {
                //worker.ReportProgress(0, new ErrorEventArgs("WinSpec can't create output image"));
                //frm.InformUserAboutWSError(new ErrorEventArgs("WinSpec can't create output image"));
                OnWSErrorEvent(new WSState(-1,"WinSpec can't create output image"));
                return;
            }
                
            //wsOutput.
            for (short i = 1; i <= x; i++)
                for (short j = 1; j <= y; j++)
                    wsOutput.PutPixel(1, j, i, 0);
            wsOutput.Save();
            /*
            IDocWindow outWnd = wsOutput.GetWindow();
            CalibObj calib = outWnd.GetCalibration();
            calib.DisplayUnit = XCALIBUNIT.XW_SYSTEM;
            calib.InputUnit = XCALIBUNIT.XW_SYSTEM;
            calib.UsageFlag = CALIB_USAGE.CALIB_OFF;
            outWnd.SetCalibration(calib);
             * */

            CalibObj calib = wsOutput.GetCalibration();
            calib.DisplayUnit = XCALIBUNIT.XW_SYSTEM;
            calib.InputUnit = XCALIBUNIT.XW_SYSTEM;
            calib.UsageFlag = CALIB_USAGE.CALIB_OFF;         

            wsOutput.SetCalibration(calib);

            wsExp = new ExpSetupClass();
            

            object xdouble = dataType.X_DOUBLE;
            wsExp.SetParam(EXP_CMD.EXP_DATATYPE, ref xdouble);
            
            
            wsDiffSpectra = null;
            wsDiffInitialized = false;
        }

        private void createDiffDoc()
        {
            DocInfoClass di = new DocInfoClass();
            di.X = expXDim;
            di.Y = 1;
            di.Z = outX * outY;
            di.dataType = dataType.X_DOUBLE;
            di.bShowWindow = true;
            di.bAppend = false;
            di.FileType = docType.dt_SPE;
            wsDiffSpectra = new DocFileClass();
            wsDiffInitialized = true;
            if (!wsDiffSpectra.OpenNew("", di))
            {
                //frm.InformUserAboutWSError(new ErrorEventArgs("WinSpec can't differential spectra output file"));
                //worker.ReportProgress(0, new ErrorEventArgs("WinSpec can't differential spectra output file"));
                OnWSErrorEvent(new WSState(-1,"WinSpec can't differential spectra output file"));
                return;
            }
        }

        /// <summary>
        /// Called before acquision of every row in the output 2D image
        /// </summary>
        public void ExpInitRow()
        {
        }


        /// <summary>
        /// Called before acquision of every point in the output 2D image
        /// </summary>
        public void ExpInitStep()
        {            
            wsDiff = null;
            arrayWsDiff = null;
        }

        


        private void takeFrame(ref object frame)
        {
            wsDoc.GetFrame(1, ref frame);
        }

        private void despikeFramePair(object frameA, object frameB)
        {
            //Type type = frameA.GetType();
            //System.Convert.ChangeType(frameA, type)
            double[,] arrayFrameA = (double[,])frameA;
            double[,] arrayFrameB = (double[,])frameB;
            for (int i = 0; i < expXDim; i++)
            {
                Double a, b, c;
                a = arrayFrameA[i, 0];
                b = arrayFrameB[i, 0];
                c = a + b;
                if (Math.Abs(a - b) > spikeThreshould * Math.Sqrt(Math.Abs(c)))
                    arrayFrameA[i, 0] = 2 * ((a > b) ? b : a);
                else
                    arrayFrameA[i, 0] = c;
            }
        }

        private void updateDiff()
        {
            double[,] arrayWsFrame0 = (double[,])wsFrame0;
            double[,] arrayWsFrame1 = (double[,])wsFrame1;
            if (arrayWsDiff == null)
            {

                arrayWsDiff=(double[,])arrayWsFrame0.Clone();
                //wsDiff = wsFrame0
                for (int i = 0; i < expXDim; i++)
                    arrayWsDiff[i, 0] = (arrayWsFrame1[i, 0] - arrayWsFrame0[i, 0])/cycles;
            }
            else
                for (int i = 0; i < expXDim; i++)
                    arrayWsDiff[i, 0] = arrayWsDiff[i, 0] + (arrayWsFrame1[i, 0] - arrayWsFrame0[i, 0])/cycles;
            wsDiff = arrayWsDiff;
        }

        /// <summary>
        /// Called after every acquisition shot
        /// </summary>
        /// <param name="genOn">Generator currently on</param>
        /// <param name="index">Current shot index (0,1)</param>
        public void ExpProcessStep(bool genOn, long index)
        {
            //object frameA = null;
            if (index == 0)
                if (genOn)
                {
                    //frame with RF on
                    wsFrame1 = null;
                    takeFrame(ref wsFrame1);
                }
                else
                {
                    //frame with RF off
                    wsFrame0 = null;
                    takeFrame(ref wsFrame0);
                }
            else
            {
                object frameB = null;
                takeFrame(ref frameB);
                if (genOn)
                {
                    despikeFramePair(wsFrame1, frameB);
                    
                }
                else
                {
                    despikeFramePair(wsFrame0, frameB);
                    //now we have both despiked frames
                    updateDiff();
                }
                
            }

        }

        /// <summary>
        /// Called after acquision of every point in the output 2D image
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="Cycles">Number of acquisition cycles</param>
        public void ExpStoreStep(short x, short y)
        {
            double sum;
            long i;
            if (fltWnd <= 1) //Just integrate modulus
            {
                sum = 0;
                for (i = 0; i < expXDim; i++)
                    sum = sum + Math.Abs(arrayWsDiff[i, 0]);

            }
            else        //Sliding average filtering
            {
                double flt;
                flt = 0;
                for (i = 0; i < fltWnd; i++)
                    flt = flt + arrayWsDiff[i, 0];

                sum = Math.Abs(flt);
                for (i = 0; i < expXDim - fltWnd; i++)
                {
                    flt = flt - arrayWsDiff[i, 0] + arrayWsDiff[i + fltWnd, 0];
                    sum = sum + Math.Abs(flt);
                }
                sum = sum / fltWnd;
            }
            //sum = sum / cycles;
            wsOutput.PutPixel(1, (short)(y + 1), (short)(x + 1), sum);
            wsOutput.Save();
            wsOutput.Update();
            if (keepSpec)
            {
                if (!wsDiffInitialized)
                    createDiffDoc();
                wsDiffSpectra.PutFrame((short)(1 + x + y * outX), ref wsDiff);
            }
        }

        /// <summary>
        /// Called after acquision of every row in the output 2D image
        /// </summary>
        /// <param name="y"></param>
        public void ExpStoreRow(long y)
        {

        }

        /// <summary>
        /// Called after acquision of whole output 2D image
        /// </summary>
        public void ExpStore()
        {
            object rf = 1;
            wsOutput.SetParam(DM_CMD.DM_LASTFRAMERDY, ref rf);
        }

        public bool IsExpCompleted()
        {
            bool running;
            short pRes;
            running = (bool)wsExp.GetParam(EXP_CMD.EXP_RUNNING,out pRes);
            return !running;
        }

        private void RunExp(BackgroundWorker worker, DoWorkEventArgs e)
        {
            //IDocFile df;
            bool res=wsExp.Start(ref wsDoc);
            if (!res)
            {
                //worker.ReportProgress(0, new WSState(-1,"WinSpec can't start experiment"));
                //frm.OnWSError(this,new ErrorEventArgs("WinSpec can't start experiment"));
                OnWSErrorEvent(new WSState(-1, "WinSpec can't start experiment"));
                return;
            }
            //wsDoc = df;
            short pbRes;            
            wsExp.WaitForExperiment();
            if (expXDim == 0)
                expXDim = (int)wsDoc.GetParam(DM_CMD.DM_XDIM, out pbRes);
        }   
        

        

    }
}
